package com.qs.redis.jedis.lock;

import com.qs.redis.utils.JedisConnectionUtils;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.params.SetParams;

import java.util.UUID;

/**
 * 使用redis实现分布式锁
 */
public class DistributeLock {

    /**
     * 获取分布式锁
     *
     * @param lockName       锁的名词
     * @param acquireTimeout 获得锁的超时时间
     * @param lockTimeout    锁本身的过期时间
     * @return 返回锁的标识
     */
    public String acquireLock(String lockName, long acquireTimeout, long lockTimeout) {
        //唯一标识获得的锁，保证释放的和获取的是同一个锁
        String identifier = UUID.randomUUID().toString();
        String lockKey = "lock:" + lockName;
        //过期时间转化为秒
        int lockExpire = (int) (lockTimeout / 1000);
        Jedis jedis = null;
        try {
            //获取redis连接
            jedis = JedisConnectionUtils.getJedis();
            //获取锁的过期时间为：当前时间+设置锁的获取过期时间
            long end = System.currentTimeMillis() + acquireTimeout;

            //在限定的时间内不断循环重试获取锁
            while (System.currentTimeMillis() < end) {
                SetParams setParams = new SetParams();
                setParams.nx();
                setParams.ex(lockExpire);
                //构建SetParams对象，将setnx(lock,identifier)和expire(lock,lockExpire)合并为一个原子操作，在获取锁的时候，给锁设置过期时间,
                //该操作成功，返回"OK"，失败返回null
                if ("OK".equals(jedis.set(lockKey, identifier, setParams))) {
                    //获得锁成功，返回锁的标识
                    return identifier;
                } else {
                    //如果锁对应的key未设置过期时间，则设置过期时间，避免锁无法释放
                    if (jedis.ttl(lockKey) == -1) {
                        jedis.expire(lockKey, lockExpire);
                    }
                }


                //等待片刻后，进行获取锁的重试
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }

        return null;
    }

    /**
     * 释放分布式锁
     * <p>
     * 使用redis的watch命令，监控key是否被其他线程(进程)修改，如果被修改，则该线程(进程)所在的事物就不执行，返回null
     */
    public boolean releaseLock(String lockName, String identifier) {
        System.out.println(lockName + "开始释放锁：" + identifier);
        String lockKey = "lock:" + lockName;
        Jedis jedis = null;
        boolean isRelease = false;
        try {
            jedis = JedisConnectionUtils.getJedis();
            while (true) {
                jedis.watch(lockKey);
                //判断释放的锁和获取的锁是否为同一把锁
                if (identifier.equals(jedis.get(lockKey))) {
                    //开启事务
                    Transaction transaction = jedis.multi();
                    //删除锁对应key
                    transaction.del(lockKey);
                    //提交事务（监听期间key被修改，则回滚事务）
                    if (transaction.exec().isEmpty()) {
                        continue;
                    }

                    //锁释放成功
                    isRelease = true;
                }

                //todo 在unwatch之前出现异常？
                jedis.unwatch();
                break;
            }
        } finally {
            if (jedis != null) {
                jedis.close();
            }
        }

        return isRelease;
    }

    /**
     * redis+lua实现锁释放
     */
    public boolean releaseLockWithLua(String lockName, String identifier) {
        System.out.println(lockName + "开始释放锁：" + identifier);

        Jedis jedis = JedisConnectionUtils.getJedis();
        String lockKey = "lock:" + lockName;

        String lua = "if redis.call(\"get\",KEYS[1])==ARGV[1] then " +
                "return redis.call(\"del\",KEYS[1]) " +
                "else return 0 end";
        Long rs = (Long) jedis.eval(lua, 1, new String[]{lockKey, identifier});
        if (rs.intValue() > 0) {
            return true;
        }
        return false;

    }

}
